import { isNil } from 'lodash-es'
import { reactive, ref, Ref, UnwrapRef } from 'vue'
import { appConfig } from '../config/app';
import { addFormat } from '../format';
import { BaseType, EnumProps, IEnum } from '../index';


export type EnumClassName = keyof IEnum | string

const data: Record<string, Ref<EnumProps[]>> = {}
const dataByKey: Record<string, Record<string, EnumProps>> = {}
const dataByValue: Record<string, Record<BaseType, EnumProps>> = {}
const dataApi: Record<string, () => Promise<EnumProps[]> | EnumProps[]> = {}

const initData = (clazz: string, arr: any[]): void => {
  data[clazz].value = arr
  for (const item of arr) {
    if (!isNil(item.key)) {
      dataByKey[clazz][item.key] = item
    }
    if (!isNil(item.value)) {
      dataByValue[clazz][item.value] = item
    }
  }
}

async function listEnumsByClass(clazz: string, appKey?: string): Promise<Ref<UnwrapRef<EnumProps[]>>> {
  try {
    const api = dataApi[clazz]
    if (api) {
      initData(clazz, await api())
    } else {
      const arr = await appConfig.enum.listEnumsByClass(clazz, appKey)
      initData(clazz, arr)
    }
  } catch {
    initData(clazz, [])
  }
  return data[clazz]
}

export function listEnums(clazz: EnumClassName, appKey?: string, cache: boolean = appConfig.enum.cache): Ref<UnwrapRef<EnumProps[]>> {
  const out = data[clazz]
  if (out?.value) {
    cache || listEnumsByClass(clazz as string, appKey)
    return out
  } else {
    data[clazz] = ref([])
    dataByKey[clazz] = reactive({})
    dataByValue[clazz] = reactive({})
    listEnumsByClass(clazz as string, appKey)
    return data[clazz]
  }
}

export function getEnumByKey(key: any, clazz: EnumClassName, appKey?: string, cache?: boolean): EnumProps {
  const list = data[clazz]
  if (list?.value) {
    cache || listEnums(clazz, appKey)
  } else {
    listEnums(clazz, appKey)
  }
  return dataByKey[clazz][key]
}

export function getEnumByValue(val: any, clazz: EnumClassName, appKey?: string, cache?: boolean): EnumProps {
  const list = data[clazz]
  if (list?.value) {
    cache || listEnums(clazz, appKey)
  } else {
    listEnums(clazz, appKey)
  }
  return dataByValue[clazz][val]
}

export function getEnum(val: any, clazz: EnumClassName, appKey?: string, cache?: boolean): EnumProps {
  if (appConfig.enum.valueKey === 'value') {
    return getEnumByValue(val, clazz, appKey, cache)
  } else {
    return getEnumByKey(val, clazz, appKey, cache)
  }
}

export function addEnum(clazz: string, fn: () => Promise<EnumProps[]> | EnumProps[]): void {
  dataApi[clazz] = fn
}

addEnum('Boolean', () => [{ key: 'true', title: '是', value: true }, { key: 'false', title: '否', value: false }])

addFormat('Enum', (val: any, clazz: EnumClassName) => getEnum(val, clazz, undefined, true)?.title ?? '')
addFormat('EnumValue', (val: any, clazz: EnumClassName) => getEnumByValue(val, clazz, undefined, true)?.title ?? '')
addFormat('EnumKey', (key: string, clazz: EnumClassName) => getEnumByKey(key, clazz, undefined, true)?.title ?? '')

declare module '../index' {
  interface IFormat {
    Enum: (val: any, clazz: string) => string
    EnumKey: (key: any, clazz: string) => string
    EnumValue: (val: any, clazz: string) => string
  }
}
